﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace Djs.Common.Components
{
    /// <summary>
    /// Size axis calculations, convertor to visual representation and direct visualisation to graphic layer
    /// </summary>
    public class GSizeAxis : InteractiveItem, IInteractiveItem
    {
        #region Constructor, public properties and events
        public GSizeAxis()
        {
            this._Arrangement = new Arrangement(this);
            this.InitProperties();
        }
        protected void InitProperties()
        {
            this.Enabled = true;
            this.BackColor = Color.FromArgb(248, 252, 255);
            this.BackColor3DEffect = 0.12f;
            this.ScaleMin = 0.000001m;
            this.ScaleMax = 1000000m;
            this._ValueSet(new SizeRange(0, 100), true, false, false, false);
        }
        /// <summary>
        /// Color of background of axis
        /// </summary>
        public Color BackColor { get; set; }
        /// <summary>
        /// 3D effect on BackColor
        /// </summary>
        public float BackColor3DEffect { get; set; }
        /// <summary>
        /// Current list of all ticks
        /// </summary>
        public IEnumerable<SizeAxisTick> TickList { get { return this._TickList; } }
        #region Value & Scale
        /// <summary>
        /// Visible time range of size axis.
        /// Setting invoke events ValueChange and DrawRequest.
        /// </summary>
        public SizeRange Value
        {
            get { return this._Value; }
            set { this._ValueSet(value, true, true, true, true); }
        }
        /// <summary>
        /// Visible time range of size axis.
        /// Setting invoke events ValueChange, but no DrawRequest.
        /// </summary>
        public SizeRange ValueHide
        {
            get { return this._Value; }
            set { this._ValueSet(value, true, true, true, false); }
        }
        /// <summary>
        /// Visible time range of size axis.
        /// Setting invoke events SizeRangeChange and DrawRequest, but no SyncOther().
        /// </summary>
        protected SizeRange ValueSynchronized
        {
            get { return this._Value; }
            set { this._ValueSet(value, true, true, false, true); }
        }
        /// <summary>
        /// Visible time range of size axis.
        /// Setting invoke no events, only internal recalculate visual data.
        /// </summary>
        public SizeRange ValueSilent
        {
            get { return this._Value; }
            set { this._ValueSet(value, true, false, true, false); }
        }
        private void _ValueSet(SizeRange value, bool callCalcScale, bool callEvents, bool callSync, bool callDrawRequest)
        {
            if (value == null) return;
            value = this._ValueAlign(value);
            bool isEqual = (value == this._Value);
            if (!isEqual)
            {
                this._Value = value;
                if (callCalcScale)
                    this._CalculateScale();
                if (callEvents)
                    this._OnValueChanged();
                if (callSync)
                    this._SyncOtherAxis();
                if (callDrawRequest)
                    this._OnDrawRequest();
            }
        }
        /// <summary>
        /// Create and align new SizeRange for current SizeAxis.
        /// New value can be "one-side" (HasOnlyBegin or HasOnlyEnd).
        /// New range is aligned to valid SizeScale for current SizeAxis.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private SizeRange _ValueAlign(SizeRange value)
        {
            if (value == null) return null;

            SizeRange sizeRange = null;
            if (value.IsFilled)
                sizeRange = value;
            else if (value.HasOnlyBegin)
                sizeRange = SizeRange.CreateFromBeginSize(value.Begin.Value, this._Value.Size.Value);
            else if (value.HasOnlyEnd)
                sizeRange = SizeRange.CreateFromSizeEnd(this._Value.Size.Value, value.End.Value);
            else
                return null;

            // SizeScale without aligning (to MIN-MAX range):
            if (this.PixelSize <= 0m) return sizeRange;
            decimal? sizeScale = _GetScaleFromSizeRange(sizeRange, false);
            if (!sizeScale.HasValue) return null;
            if (!_IsSizeScaleInRange(sizeScale.Value))
            {   // Zoom SizeRange to valid SizeScale, with center on center of SizeRange:
                decimal validSizeScale = _ScaleAlign(sizeScale.Value);
                decimal milimeterInScale = (validSizeScale * this.PixelSize);  // sizescale = milimeter / 1px; milimeterInScale = milimeter on current pixel size
                sizeRange = sizeRange.ZoomToRatio(0.5m, milimeterInScale);     // new SizeRange with specified Time, with center on center original SizeRange
            }

            // Crop to this.MaxSizeRange:
            sizeRange = this._ValueAlignToValueMax(sizeRange);

            return sizeRange;
        }
        /// <summary>
        /// Returns a specified SizeRange cropped to this.MaxSizeRange
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private SizeRange _ValueAlignToValueMax(SizeRange value)
        {
            SizeRange valueMax = this._ValueMax ;
            if (value == null || valueMax== null) return value;
            if (!value.IsFilled) return value;
            decimal size = value.Size.Value;
            if (valueMax.End.HasValue && value.End.Value > valueMax.End.Value)
                value = SizeRange.CreateFromSizeEnd(size, valueMax.End.Value);
            if (valueMax.Begin.HasValue && value.Begin.Value < valueMax.Begin.Value)
                value = SizeRange.CreateFromBeginSize(valueMax.Begin.Value, size);
            if (valueMax.End.HasValue && value.End.Value > valueMax.End.Value)
                value = valueMax.Clone;
            return value;
        }
        /// <summary>
        /// Maximum SizeRange for this Axis.
        /// Can be null (this is: without limits).
        /// If is set, then current SizeRange can not be outside specified value.
        /// </summary>
        public SizeRange ValueMax
        {
            get { return this._ValueMax; }
            set { this._ValueMax = value; this.Value = this._ValueAlign(this.Value); }
        }
        private SizeRange _ValueMax;
        /// <summary>
        /// Number of milimeter to one pixel (in range 1 nanometer to 1,000 kilometers to 20 pixels)
        /// </summary>
        public decimal Scale
        {
            get { return this._Scale; }
            set { if (value != this._Scale) this._SetScale(value); }
        }
        /// <summary>
        /// Store a scale to this, recalculate Value and invoke events
        /// </summary>
        /// <param name="value"></param>
        private void _SetScale(decimal value)
        {
            decimal scale = this._ScaleAlign(value);
            decimal size = scale * (decimal)this.PixelSize;
            this.Value = this.Value.ZoomToSize(0.5d, size);
        }
        /// <summary>
        /// Actual Minimal scale = smallest displayed SizeRange : set value in milimeters per pixel.
        /// </summary>
        public decimal ScaleMin { get { return this._SizeScaleMin; } set { this._SizeScaleMin = value; this._Scale = _ScaleAlign(this._Scale); } } private decimal _SizeScaleMin;
        /// <summary>
        /// Actual Maximal scale = greatest displayed SizeRange : set value in milimeters per pixel.
        /// </summary>
        public decimal ScaleMax { get { return this._SizeScaleMax; } set { this._SizeScaleMax = value; this._Scale = _ScaleAlign(this._Scale); } } private decimal _SizeScaleMax;
        /// <summary>
        /// Current value (displayed range Begin ÷ End)
        /// </summary>
        private SizeRange _Value;
        /// <summary>
        /// Current scale = Number of milimeter to one pixel
        /// </summary>
        private decimal _Scale;
        #endregion
        #region Bounds
        /// <summary>
        /// Coordinates on a control (typically GInteractiveControl), where SizeAxis are drawed and are active.
        /// Setting invoke events AreaChange and DrawRequest.
        /// </summary>
        public Rectangle Bounds
        {
            get { return this._Bounds; }
            set
            {
                if (value != this._Bounds)
                {
                    this._Bounds = value;
                    this.AutoDetectOrientation(false);
                    this._CalculateEnd();
                    this._OnBoundsChanged();
                    this._OnDrawRequest();
                }
            }
        }
        /// <summary>
        /// Coordinates of this Axis on a control (typically GInteractiveControl), where SizeAxis are drawed and are active.
        /// Setting invoke no events, only internal shifts Value.Begin (if necessary) and recalculate visual data and Ticks.
        /// </summary>
        public Rectangle BoundsInternal
        {
            get { return this._Bounds; }
            set
            {
                if (value != this.__Bounds)
                {
                    this._RecalcValueOnBoundsChangeToValue(this.__Bounds, value);
                    this._Bounds = value;
                    this._CalculateTicks();
                }
            }
        }
        /// <summary>
        /// Recalculate Value (shift Begin and End) when Bounds.Location are changed.
        /// After move a visual begin (= bounds.Location) of axis: leave "content of axis" on its coordinates, only shift Value.Begin.
        /// </summary>
        /// <param name="oldBounds"></param>
        /// <param name="newBounds"></param>
        private void _RecalcValueOnBoundsChangeToValue(Rectangle oldBounds, Rectangle newBounds)
        {
            SizeRange value = this.Value;
            if (!value.IsFilled) return;
            Decimal? begin = this.GetValueForAbsolutePoint(newBounds.Location);
            if (!begin.HasValue) return;
            if (begin.Value == value.Begin.Value) return;
            value = SizeRange.CreateFromBeginSize(begin.Value, value.Size.Value);
            this._ValueSet(value, false, false, false, true);
        }
        /// <summary>
        /// Coordinates of this Axis on a control (typically GInteractiveControl), where SizeAxis are drawed and are active.
        /// Setting does not calculate ticks or change Value, only set bounds and ActiveBounds, and set RepaintAllItems = true;.
        /// </summary>
        private Rectangle _Bounds
        {
            get { return this.__Bounds; }
            set
            {
                this.__Bounds = value;
                this.ActiveBounds = value;
                this.RepaintAllItems = true;
            }
        }
        private Rectangle __Bounds;
        #endregion
        #endregion
        #region Eventy a jejich invokery
        /// <summary>
        /// Event on this.SizeRange change
        /// </summary>
        public event EventHandler ValueChanged;
        private void _OnValueChanged()
        {
            this.OnValueChanged();
            if (this.ValueChanged != null)
                this.ValueChanged(this, EventArgs.Empty);
        }
        protected virtual void OnValueChanged()
        { }
        /// <summary>
        /// Event on this.Bounds change
        /// </summary>
        public event EventHandler BoundsChanged;
        private void _OnBoundsChanged()
        {
            this.OnBoundsChanged();
            if (this.BoundsChanged != null)
                this.BoundsChanged(this, EventArgs.Empty);
        }
        protected virtual void OnBoundsChanged()
        { }
        /// <summary>
        /// Event on this.DrawRequest occured
        /// </summary>
        public event EventHandler DrawRequest;
        private void _OnDrawRequest()
        {
            this.RepaintToLayers = GInteractiveDrawLayer.Standard;
            this.OnDrawRequest();
            if (this.DrawRequest != null)
                this.DrawRequest(this, EventArgs.Empty);
        }
        protected virtual void OnDrawRequest()
        { }
        #endregion
        #region Synchronize Master-Slave SizeAxis
        public List<GSizeAxis> SynchronizedSlaveAxisList
        {
            get
            {
                if (this._SynchronizedSizeAxisList == null)
                    this._SynchronizedSizeAxisList = new List<GSizeAxis>();
                return this._SynchronizedSizeAxisList;
            }
        }
        private List<GSizeAxis> _SynchronizedSizeAxisList;
        /// <summary>
        /// Mode for synchronize from other (master, active) axis, to this (slave) axis
        /// </summary>
        public AxisSynchronizeMode SynchronizeMode { get; set; }
        protected void _SyncOtherAxis()
        {
            foreach (GSizeAxis slaveAxis in this.SynchronizedSlaveAxisList)
                slaveAxis._SyncCurrentAxisFrom(this);
        }
        /// <summary>
        /// Synchronize this (slave) axis from other (master) axis
        /// </summary>
        /// <param name="masterAxis"></param>
        private void _SyncCurrentAxisFrom(GSizeAxis masterAxis)
        {
            switch (this.SynchronizeMode)
            {
                case AxisSynchronizeMode.None: break;
                case AxisSynchronizeMode.Range:
                    this.ValueSynchronized = masterAxis.Value;
                    break;
                case AxisSynchronizeMode.Scale:
                    decimal masterScale = masterAxis._Scale;
                    if (masterScale != this._Scale)
                    {
                        SizeRange sizeRange = this.Value;
                        Decimal? size = _GetSizeFromScale(masterScale, true);
                        if (size.HasValue)
                            this.ValueSynchronized = sizeRange.ZoomToSize(0.5d, size.Value);
                    }
                    break;
            }
        }
        #endregion
        #region AxisOrientation, methods and properties
        /// <summary>
        /// Axis orientation, must comply with Bounds (if not, will be automatically changed)
        /// </summary>
        public AxisOrientation Orientation
        {
            get { return this._Orientation; }
            set { this._Orientation = value; this._OrientationUser = value; this.AutoDetectOrientation(true); }
        }
        /// <summary>
        /// Current orientation by Bounds
        /// </summary>
        private AxisOrientation _Orientation;
        /// <summary>
        /// Orientation requested by user (application code)
        /// </summary>
        private AxisOrientation? _OrientationUser;
        /// <summary>
        /// Vrací vzdálenost pixelu od výchozího bodu osy, v aktuální orientaci a při aktuálních rozměrech osy.
        /// Pixel je v absolutních souřadnicích.
        /// </summary>
        /// <param name="absolutePoint"></param>
        /// <returns></returns>
        protected int GetPixelForAbsolutePointByOrientation(Point absolutePoint)
        {
            return this.GetPixelForRelativePointByOrientation(absolutePoint.Sub(this.__Bounds.Location));
        }
        /// <summary>
        /// Vrací vzdálenost pixelu od výchozího bodu osy v aktuální orientaci a aktuálních rozměrech osy.
        /// Pixel je v relativních souřadnicích.
        /// </summary>
        /// <param name="relativePoint"></param>
        /// <returns></returns>
        protected int GetPixelForRelativePointByOrientation(Point relativePoint)
        {
            int h = this.__Bounds.Height;
            switch (this.Orientation)
            {
                case AxisOrientation.Top: return relativePoint.X;
                case AxisOrientation.LeftUp: return h - relativePoint.Y;
                case AxisOrientation.LeftDown: return relativePoint.Y;
                case AxisOrientation.RightUp: return h - relativePoint.Y;
                case AxisOrientation.RightDown: return relativePoint.Y;
                case AxisOrientation.Bottom: return relativePoint.X;
            }
            return 0;
        }
        /// <summary>
        /// Vrací souřadnici pixelu na ose X nebo Y podle orientace, kde hodnota relativePixel vyjadřuje vzdálenost cílového pixelu od počátku osy (Begin).
        /// </summary>
        /// <param name="relativePixel"></param>
        /// <returns></returns>
        protected int GetAbsolutePixelForRelativeByOrientation(int relativePixel)
        {
            Rectangle bounds = this.__Bounds;
            int x = bounds.X;
            int y = bounds.Y;
            int b = bounds.Bottom;
            switch (this.Orientation)
            {
                case AxisOrientation.Top: return x + relativePixel;
                case AxisOrientation.LeftUp: return b - relativePixel;
                case AxisOrientation.LeftDown: return y + relativePixel;
                case AxisOrientation.RightUp: return b - relativePixel;
                case AxisOrientation.RightDown: return y + relativePixel;
                case AxisOrientation.Bottom: return x + relativePixel;
            }
            return 0;
        }
        /// <summary>
        /// Return a shift in pixel in current Orientation, from shift (in Point on control).
        /// For orientation Top return X, for orientation LeftDown return Y, for orientation LeftUp return -Y, and so on.
        /// </summary>
        /// <param name="shift"></param>
        /// <param name="orientation"></param>
        /// <returns></returns>
        protected int GetShiftByOrientation(Point shift)
        {
            return GetShiftByOrientation(shift, this.Orientation);
        }
        /// <summary>
        /// Return a shift in pixel in specified orientation, from shift (in Point on control).
        /// For orientation Top return X, for orientation LeftDown return Y, for orientation LeftUp return -Y, and so on.
        /// </summary>
        /// <param name="shift"></param>
        /// <param name="orientation"></param>
        /// <returns></returns>
        protected static int GetShiftByOrientation(Point shift, AxisOrientation orientation)
        {
            switch (orientation)
            {
                case AxisOrientation.Top: return shift.X;
                case AxisOrientation.LeftUp: return -shift.Y;
                case AxisOrientation.LeftDown: return shift.Y;
                case AxisOrientation.RightUp: return -shift.Y;
                case AxisOrientation.RightDown: return shift.Y;
                case AxisOrientation.Bottom: return shift.X;
            }
            return 0;
        }
        /// <summary>
        /// Accomodate this.Orientation to this._Bounds
        /// </summary>
        protected void AutoDetectOrientation(bool callOnDrawRequest)
        {
            AxisOrientation orientationOld = this._Orientation;

            Rectangle bounds = this.__Bounds;
            bool isHorizontal = (bounds.Width > bounds.Height);
            bool hasUserOrientation = this._OrientationUser.HasValue;
            if (isHorizontal)
            {   // Current bounds are horizontal:
                if (hasUserOrientation && (this._OrientationUser.Value == AxisOrientation.Top || this._OrientationUser.Value == AxisOrientation.Bottom))
                {   // Have a user request to orientation, and is horizontal => use it:
                    this._Orientation = this._OrientationUser.Value;
                }
                else
                {   // does not have a correct user orientation, set current orientation by position of axis:
                    this._Orientation = ((bounds.Y < 100) ? AxisOrientation.Top : AxisOrientation.Bottom);
                }
            }
            else
            {   // Current bounds are vertical:
                if (hasUserOrientation && (this._OrientationUser.Value == AxisOrientation.LeftUp || this._OrientationUser.Value == AxisOrientation.LeftDown || this._OrientationUser.Value == AxisOrientation.RightUp || this._OrientationUser.Value == AxisOrientation.RightDown))
                {   // Have a user request to orientation, and is horizontal => use it:
                    this._Orientation = this._OrientationUser.Value;
                }
                else
                {   // does not have a correct user orientation, set current orientation by position of axis:
                    this._Orientation = ((bounds.X < 100) ? AxisOrientation.LeftDown: AxisOrientation.RightDown);
                }
            }

            if (this._Orientation != orientationOld && callOnDrawRequest)
                this._OnDrawRequest();
        }
        /// <summary>
        /// WinForm Orientation (Horizontal/Vertical) according by this.Orientation
        /// </summary>
        protected System.Windows.Forms.Orientation PaintOrientation
        {
            get
            {
                switch (this.Orientation)
                {
                    case AxisOrientation.Top:
                    case AxisOrientation.Bottom:
                        return System.Windows.Forms.Orientation.Horizontal;
                    case AxisOrientation.LeftUp:
                    case AxisOrientation.LeftDown:
                    case AxisOrientation.RightUp:
                    case AxisOrientation.RightDown:
                        return System.Windows.Forms.Orientation.Vertical;
                }
                return System.Windows.Forms.Orientation.Horizontal;
            }
        }
        /// <summary>
        /// Current first pixel (as decimal), by this.Orientation returns: X or Y or Bottom-1
        /// </summary>
        protected decimal PixelFirst
        {
            get
            {
                Rectangle b = this.__Bounds;
                switch (this.Orientation)
                {
                    case AxisOrientation.Top: return b.X;
                    case AxisOrientation.LeftUp: return b.Bottom - 1;
                    case AxisOrientation.LeftDown: return b.Y;
                    case AxisOrientation.RightUp: return b.Bottom - 1;
                    case AxisOrientation.RightDown: return b.Y;
                    case AxisOrientation.Bottom: return b.X;
                }
                return b.X;
            }
        }
        /// <summary>
        /// Current size in pixel (as decimal), by this.Orientation returns: Width or Height
        /// </summary>
        protected decimal PixelSize
        {
            get
            {
                Rectangle b = this.__Bounds;
                switch (this.Orientation)
                {
                    case AxisOrientation.Top: return b.Width;
                    case AxisOrientation.LeftUp: return b.Height;
                    case AxisOrientation.LeftDown: return b.Height;
                    case AxisOrientation.RightUp: return b.Height;
                    case AxisOrientation.RightDown: return b.Height;
                    case AxisOrientation.Bottom: return b.Width;
                }
                return b.X;
            }
        }
        #endregion
        #region Interactive property and methods
        /// <summary>
        /// true when this control is interactive (current SizeRange can be moved / zoom / shift).
        /// true is default.
        /// </summary>
        public bool Enabled { get { return this.IsEnabled; } set { this.IsEnabled = value; } }
        protected override GInteractiveStyles Style { get { return GInteractiveStyles.AllInteractivity; } }
        /// <summary>
        /// Interactive action handler
        /// </summary>
        /// <param name="e"></param>
        protected override void StateChanged(GInteractiveChangeStateArgs e)
        {
            e.ToolTip = "";
            switch (e.ChangeState)
            {
                case GInteractiveChangeState.MouseOver:
                    this._MouseOverPoint = e.MouseRelativePoint;
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;   // Can not draw only "Interactive" layer, because _MouseOverPoint is drawed under Ticks, thus after draw "only" MousePoint is necessary draw ticks too...
                    e.ToolTip = this._CreateToolTip(e.MouseRelativePoint);
                    break;
                case GInteractiveChangeState.MouseOverDisabled:
                    e.ToolTip = this._CreateToolTip(e.MouseRelativePoint);
                    break;
                case GInteractiveChangeState.LeftDown:
                    this._MouseOverPoint = null;
                    this._MouseDownPoint = e.MouseRelativePoint;
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    e.ToolTip = this._CreateToolTip(e.MouseRelativePoint);
                    break;
                case GInteractiveChangeState.LeftDragBegin:
                    if ((Control.ModifierKeys & Keys.Control) == 0)
                    {   // Shift:
                        this._InteractiveShiftOrigin = this.Value.Begin;
                        e.UserDragPoint = this.Bounds.Location;
                        e.RequiredCursorType = SysCursorType.Hand;
                    }
                    else
                    {   // Zoom:
                        this._InteractiveZoomOrigin = this.Value.Clone;
                        this._InteractiveZoomCenter = this.GetValueForRelativePoint(e.MouseRelativePoint.Value);
                        e.UserDragPoint = this.Bounds.Location;
                        e.RequiredCursorType = ((this.PaintOrientation == System.Windows.Forms.Orientation.Horizontal) ? SysCursorType.SizeWE : SysCursorType.SizeNS);
                    }
                    break;
                case GInteractiveChangeState.LeftDragMove:
                    if (e.UserDragPoint.HasValue )
                    {
                        SizeRange valueOld = this.Value;
                        Point shift = this.Bounds.Location.Sub(e.UserDragPoint.Value);
                        int pixel = this.GetShiftByOrientation(shift);
                        if (this._InteractiveShiftOrigin.HasValue)
                        {   // Shift:
                            Decimal begin = this._InteractiveShiftOrigin.Value + this.GetDistanceForPixel(pixel).Value;
                            begin = this._CurrentSet.RoundValueToTick(begin, AxisTickType.PixelTick).Value;
                            this.ValueHide = new SizeRange(begin, null);

                            if (this._MouseDownPoint.HasValue)
                                this._MouseDownPoint = e.MouseRelativePoint.Value;       // ??? new Point(e.MouseRelativePoint.Value.X, this._MouseDownPoint.Value.Y);
                            e.ToolTip = this._CreateToolTip(this.Value);
                        }
                        else if (this._InteractiveZoomCenter.HasValue)
                        {   // Zoom:
                            int silent = 5;                                              // Silent zone = 5 pixel with ratio = 1
                            pixel = (pixel >= silent ? pixel - silent : (pixel < -silent ? pixel + silent : 0));
                            SizeRange zoomSize = null;
                            if (pixel == 0)
                            {
                                zoomSize = this._InteractiveZoomOrigin.Clone;
                            }
                            else
                            {
                                double expon = (double)pixel / 40d;                      // Each 40 pixels double / half scale
                                double ratio = Math.Pow(2D, expon);                      // Coefficient to zoom of original SizeRange
                                zoomSize = this._InteractiveZoomOrigin.ZoomToRatio(this._InteractiveZoomCenter.Value, ratio);
                                decimal? sizeScale = this._GetScaleFromSizeRange(zoomSize, false);
                                if (!this._IsSizeScaleInRange(sizeScale))
                                {   // Out of scale - calculate total visible milimeter (from aligned scale * pixels) and create SizeRange for this milimeter and center:
                                    decimal milimeter = this._ScaleAlign(sizeScale.Value) * this.PixelSize;
                                    zoomSize = this._InteractiveZoomOrigin.ZoomToSize(this._InteractiveZoomCenter.Value, milimeter);
                                }
                            }
                            zoomSize = this._ValueAlignToValueMax(zoomSize);
                            if (zoomSize != this._Value)
                            {
                                this.ValueHide = zoomSize;
                            }
                            e.ToolTip = this._CreateToolTip(this.Value);
                        }
                        bool isEqual = (this.Value == valueOld);
                        if (!isEqual)
                            this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    }
                    break;
                case GInteractiveChangeState.LeftDragEnd:
                    this._InteractiveShiftOrigin = null;
                    this._InteractiveZoomCenter = null;
                    this._InteractiveZoomOrigin = null;
                    e.RequiredCursorType = SysCursorType.Default;
                    e.ToolTip = this._CreateToolTip(e.MouseRelativePoint);
                    break;

                case GInteractiveChangeState.LeftUp:
                    this._MouseDownPoint = null;
                    this._MouseOverPoint = e.MouseRelativePoint;
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    e.ToolTip = this._CreateToolTip(e.MouseRelativePoint);
                    break;
                case GInteractiveChangeState.LeftDoubleClick:
                    if (this.ValueMax != null && this.ValueMax.IsFilled)
                    {
                        this.ValueHide = this.ValueMax.Clone;
                        this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    }
                    break;

                case GInteractiveChangeState.WheelUp:
                case GInteractiveChangeState.WheelDown:
                    SizeRange currentSize = this.Value;
                    if (Control.ModifierKeys == Keys.Control)
                    {   // Ctrl + Wheel = Zoom under mouse:
                        Decimal? center = this.GetValueForRelativePoint(e.MouseRelativePoint.Value);
                        if (center.HasValue)
                        {
                            double ratio = 1.15d;
                            if (e.ChangeState == GInteractiveChangeState.WheelUp) ratio = 1d / ratio;
                            this.ValueSilent = currentSize.ZoomToRatio(center.Value, ratio);
                        }
                    }
                    else
                    {   // Wheel alone = Shift:
                        Decimal coefficient = (Control.ModifierKeys == Keys.Shift ? 0.333m : 0.07m) * (e.ChangeState == GInteractiveChangeState.WheelUp ? 1m : -1m);
                        Decimal offset = coefficient * currentSize.Size.Value;
                        this.ValueSilent = new SizeRange(this.ValueSilent.Begin.Value + offset, null);
                    }
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    e.ToolTip = this._CreateToolTip(e.MouseRelativePoint);
                    break;

                case GInteractiveChangeState.MouseLeave:
                    this._MouseOverPoint = null;
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    break;
            }
        }
        /// <summary>
        /// Create and return text for tooltip for specified relative point
        /// </summary>
        /// <param name="relativePoint"></param>
        /// <returns></returns>
        private string _CreateToolTip(Point? relativePoint)
        {
            if (!relativePoint.HasValue) return null;
            Decimal? value = this.GetValueForRelativePoint(relativePoint.Value, AxisTickType.PixelTick);
            if (!value.HasValue) return null;
            return value.ToString();
        }
        /// <summary>
        /// Create and return text for tooltip for specified relative point
        /// </summary>
        /// <param name="relativePoint"></param>
        /// <returns></returns>
        private string _CreateToolTip(SizeRange sizeRange)
        {
            if (sizeRange == null) return null;
            SizeRange rounded = new SizeRange(this._CurrentSet.RoundValueToTick(sizeRange.Begin.Value, AxisTickType.PixelTick), this._CurrentSet.RoundValueToTick(sizeRange.End.Value, AxisTickType.PixelTick));
            return rounded.ToString();
        }
        private Decimal? _InteractiveShiftOrigin;
        private SizeRange _InteractiveZoomOrigin;
        private Decimal? _InteractiveZoomCenter;
        private Point? _MouseOverPoint;
        private Point? _MouseDownPoint;
        #endregion
        #region Draw to Graphic
        /// <summary>
        /// Draw current size axis to specified graphic.
        /// Coordinates are this.Area.
        /// </summary>
        /// <param name="e"></param>
        protected override void Draw(GInteractiveDrawArgs e)
        {
            if (this.Bounds.Width > 0 && this.Bounds.Height > 0 && this.IsReady && e.DrawLayer == GInteractiveDrawLayer.Standard)
            {
                this._CalculateRequired();
                using (GPainter.GraphicsUseText(e.Graphics, this.Bounds))
                {
                    this._DrawBackground(e.Graphics);
                    this._DrawMousePoint(e.Graphics);
                    this._DrawTicks(e.Graphics);
                    this._DrawInfo(e.Graphics);
                }
            }
        }
        private void _DrawBackground(Graphics graphics)
        {
            GPainter.DrawAxisBackground(graphics, this.Bounds,  this.PaintOrientation, this.Enabled, this.CurrentState, this.BackColor, this.BackColor3DEffect);
        }
        private void _DrawMousePoint(Graphics graphics)
        {
            if (this._MouseOverPoint.HasValue)
            {
                Point mousePoint = this.Bounds.Location.Add(this._MouseOverPoint.Value);
                GPainter.DrawRadiance(graphics, mousePoint, this.Bounds, Color.FromArgb(128, Color.LightYellow));
            }
            else if (this._MouseDownPoint.HasValue)
            {
                Point mousePoint = this.Bounds.Location.Add(this._MouseDownPoint.Value);
                GPainter.DrawRadiance(graphics, mousePoint, this.Bounds, Color.FromArgb(64, Color.Violet));
            }
        }
        private void _DrawTicks(Graphics graphics)
        {
            if (this._TickList == null) return;

            using (GPainterDrawAxisTick drat = new GPainterDrawAxisTick(this.Bounds, this.Orientation, false))
            {
                foreach (SizeAxisTick tick in this._TickList)
                {
                    drat.DrawTick(graphics, tick);
                }
            }
            return;
        }
        private void _DrawInfo(Graphics graphics)
        {
            if (this._CurrentSet == null) return;
            using (SolidBrush brush = new SolidBrush(Color.LightBlue))
            using (Font font = new Font(SystemFonts.DefaultFont.Name, 8f, FontStyle.Italic))
            using (StringFormat sf = new StringFormat(StringFormatFlags.NoClip))
            {
                string text = this._CurrentSet.SubTitleItem.Interval.ToString();
                SizeF size = graphics.MeasureString(text, font);
                PointF point = new PointF(this.Bounds.Right - 3, this.Bounds.Top + 1);
                RectangleF area = size.AlignTo(this.Bounds, ContentAlignment.TopCenter);
                area.X += 3;
                // RectangleF area = size.AlignTo(point, ContentAlignment.TopRight, new Size(3, 3));
                graphics.DrawString(text, font, brush, area, sf);
            }
        }
        #endregion
        #region Public services (conversion Value - Pixel)
        /// <summary>
        /// true when axis is ready (for calculation)
        /// </summary>
        public bool IsReady { get { return this._IsScalePrepared; } }
        /// <summary>
        /// Returns Decimal for specified absolute point. Point coordinates are on Control.
        /// When SizeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="absolutePoint"></param>
        /// <returns></returns>
        public Decimal? GetValueForAbsolutePoint(Point absolutePoint)
        {
            return this.GetValueForRelativePixel(this.GetPixelForAbsolutePointByOrientation(absolutePoint), AxisTickType.None);
        }
        /// <summary>
        /// Returns Decimal for specified absolute point, rounded to specified tick. Point coordinates are on Control.
        /// When SizeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="absolutePoint"></param>
        /// <param name="roundToTick"></param>
        /// <returns></returns>
        public Decimal? GetValueForAbsolutePoint(Point absolutePoint, AxisTickType roundToTick)
        {
            return this.GetValueForRelativePixel(this.GetPixelForAbsolutePointByOrientation(absolutePoint), roundToTick);
        }
        /// <summary>
        /// Returns Decimal for specified relative point. Point coordinates are on Axis (relativePoint {0,0} is == this.Bounds.Location).
        /// When SizeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="relativePoint"></param>
        /// <returns></returns>
        public Decimal? GetValueForRelativePoint(Point relativePoint)
        {
            return this.GetValueForRelativePixel(this.GetPixelForRelativePointByOrientation(relativePoint), AxisTickType.None);
        }
        /// <summary>
        /// Returns Decimal for specified relative pixel, rounded to specified tick. Pixel coordinates are on Axis (relativePoint {0,0} is == this.Bounds.Location).
        /// When SizeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="relativePoint"></param>
        /// <param name="roundToTick"></param>
        /// <returns></returns>
        public Decimal? GetValueForRelativePoint(Point relativePoint, AxisTickType roundToTick)
        {
            return this.GetValueForRelativePixel(this.GetPixelForRelativePointByOrientation(relativePoint), roundToTick);
        }
        /// <summary>
        /// Returns Decimal for specified relative pixel, rounded to specified tick. Pixel coordinates are on SizeAxis (i.e. time Begin is on pixel = 0).
        /// When SizeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="relativePoint"></param>
        /// <param name="roundToTick"></param>
        /// <returns></returns>
        public Decimal? GetValueForRelativePixel(int pixel, AxisTickType roundToTick)
        {
            Decimal? dist = this.GetDistanceForPixel(pixel);
            if (!dist.HasValue) return null;
            Decimal coord = this._Value.Begin.Value + dist.Value;
            return this._CurrentSet.RoundValueToTick(coord, roundToTick);
        }
        /// <summary>
        /// Returns a Distance for specified pixel size. For size == 0 return 0.
        /// When SizeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="shift"></param>
        /// <returns></returns>
        public Decimal? GetDistanceForPixel(int pixel)
        {
            if (!this._IsScalePrepared) return null;
            return (decimal)pixel * this._Scale;
        }
        /// <summary>
        /// Return a pixel coordinate for specified Decimal. Pixel coordinates are on Control (i.e. time Begin is on pixel = this.Bounds.X).
        /// When SizeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public Int32? GetAbsolutePixelForValue(Decimal value)
        {
            Int32? relativePixel = this.GetRelativePixelForValue(value);
            return (relativePixel.HasValue ? (Int32?)(this.GetAbsolutePixelForRelativeByOrientation(relativePixel.Value)) : (Int32?)null);
        }
        /// <summary>
        /// Return a pixel coordinate for specified Decimal. Pixel coordinates are on SizeAxis (i.e. time Begin is on pixel = 0).
        /// When SizeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public Int32? GetRelativePixelForValue(Decimal value)
        {
            if (!this._IsScalePrepared) return null;
            decimal milimeter = value - this._Value.Begin.Value;
            decimal pixel = milimeter / this._Scale;
            return (int)Math.Round(pixel, 0);
        }
        /// <summary>
        /// Returns a pixel distance for specified logical distance (value). For distance == 0m return 0.
        /// When SizeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="distance"></param>
        /// <returns></returns>
        public Int32? GetDistanceForValue(Decimal distance)
        {
            if (!this._IsScalePrepared) return null;
            decimal pixel = distance / this._Scale;
            return (int)Math.Round(pixel, 0);
        }
        /// <summary>
        /// Itentity of axis
        /// </summary>
        public string Identity
        {
            get
            {
                if (this._Value == null || !this._Value.IsFilled) return "";
                return this.PixelSize.ToString() + ":" + this._Value.Begin.Value.ToString() + "÷" + this._Value.End.Value.ToString();
            }
        }
        /// <summary>
        /// Is scale prepared?
        /// SizeRange must be Filled, SizeScale and CurrentSet prepared (can call this._CalculateScaleOnly() when need).
        /// </summary>
        private bool _IsScalePrepared
        {
            get
            {
                if (this._Value == null || !this._Value.IsFilled) return false;
                if (this._Scale <= 0m || this._CurrentSet == null)
                    this._CalculateScaleOnly();
                if (this._CurrentSet == null) return false;
                return true;
            }
        }
        #endregion
        #region Private scale calculations, tick preparations, Arrangement and ArrangementSet selections
        /// <summary>
        /// Calculate Scale from SizeRange and Bounds
        /// </summary>
        private void _CalculateScale()
        {
            if (this.PixelSize <= 0m) return;
            this._CalculateScaleOnly();
            this._CalculateTicks();
        }
        /// <summary>
        /// Calculate SizeRange.End from SizeRange.Begin and Scale
        /// </summary>
        private void _CalculateEnd()
        {
            if (this.PixelSize <= 0m) return;
            if (this._Scale <= 0m || this._CurrentSet == null) return;
            if (this._Value == null || !this._Value.Begin.HasValue) return;

            Decimal begin = this._Value.Begin.Value;
            Decimal end = begin + (this._Scale * this.PixelSize);
            this._Value = new SizeRange(begin, end);
            this._CalculateTicksOnly();
        }
        /// <summary>
        /// Calculate SizeScale and select appropriate ArrangementSet to _CurrentSet.
        /// Does not prepare Ticks.
        /// </summary>
        private void _CalculateScaleOnly()
        {
            if (this.PixelSize <= 0m) return;
            decimal? sizeScale = this._GetScaleFromSizeRange(this._Value, true);
            if (sizeScale.HasValue)
            {
                this._Scale = sizeScale.Value;
                this._CurrentSet = this._Arrangement.SelectSetForScale(sizeScale.Value);
            }
        }
        /// <summary>
        /// Returns time scale for specified SizeRange and current Bounds.
        /// Timescale can be aligned to MIN - MAX.
        /// When sizeRange is empty or this.Bounds is empty, returns null.
        /// </summary>
        /// <param name="sizeRange"></param>
        /// <param name="align"></param>
        /// <returns></returns>
        private decimal? _GetScaleFromSizeRange(SizeRange sizeRange, bool align)
        {
            if (sizeRange == null || !sizeRange.IsFilled) return null;
            decimal pixels = this.PixelSize;
            if (pixels <= 0m) return null;
            decimal sizeScale = (decimal)sizeRange.Size.Value / this.PixelSize;
            if (align)
                sizeScale = _ScaleAlign(sizeScale);
            return sizeScale;
        }
        /// <summary>
        /// From specified scale and current PixelSize return Size (logical length for scale and pixels).
        /// If this.PixelSize is not positive number, return null.
        /// Specified scale can be aligned to this.SizeScaleMin - Max (with method this._AlignSizeScale())
        /// </summary>
        /// <param name="sizeScale"></param>
        /// <param name="align"></param>
        /// <returns></returns>
        private Decimal? _GetSizeFromScale(decimal sizeScale, bool align)
        {
            decimal pixels = this.PixelSize;
            if (pixels <= 0m) return null;
            if (align)
                sizeScale = _ScaleAlign(sizeScale);
            return pixels * sizeScale;
        }
        /// <summary>
        /// Align specified sizeScale to MIN-MAX range.
        /// </summary>
        /// <param name="sizeScale"></param>
        /// <returns></returns>
        private decimal _ScaleAlign(decimal sizeScale)
        {
            if (sizeScale < this.ScaleMin) return this.ScaleMin;
            if (sizeScale > this.ScaleMax) return this.ScaleMax;
            return sizeScale;
        }
        /// <summary>
        /// Return true when sizeScale is in range (MIN - MAX).
        /// </summary>
        /// <param name="sizeScale"></param>
        /// <returns></returns>
        private bool _IsSizeScaleInRange(decimal? sizeScale)
        {
            if (!sizeScale.HasValue) return false;
            return (sizeScale >= this.ScaleMin && sizeScale <= this.ScaleMax);
        }
        /// <summary>
        /// Calculate required field : Scale (if is not a positive number) and Ticks (if is null or empty).
        /// </summary>
        private void _CalculateRequired()
        {
            if (this._Scale <= 0m || this._CurrentSet == null)
            {
                this._CalculateScaleOnly();
                this._TickList = null;
            }
            if (this._CurrentSet == null) return;
            if (this._TickList == null || this._TickList.Count == 0)
            {
                this._CalculateTicksOnly();
            }
        }
        /// <summary>
        /// Calculate Ticks for Scale and SizeRange. Call _CalculateScaleOnly() if need.
        /// </summary>
        private void _CalculateTicks()
        {
            if (this._Scale <= 0m || this._CurrentSet == null)
                this._CalculateScaleOnly();
            if (this._CurrentSet == null) return;
            this._CalculateTicksOnly();
        }
        /// <summary>
        /// Only calculate TimeTicks for Scale and SizeRange, does not prepare Scale
        /// </summary>
        private void _CalculateTicksOnly()
        {
            if (this._CurrentSet == null) return;
            Dictionary<Decimal, SizeAxisTick> tickDict = new Dictionary<Decimal, SizeAxisTick>();

            this._CurrentSet.AddInitialTicks(tickDict);
            this._CurrentSet.CalculateTicksLine(AxisTickType.Title, tickDict);
            this._CurrentSet.CalculateTicksLine(AxisTickType.SubTitle, tickDict);
            this._CurrentSet.CalculateTicksLine(AxisTickType.SignificantTick, tickDict);
            this._CurrentSet.CalculateTicksLine(AxisTickType.RegularTick, tickDict);

            List<SizeAxisTick> tickList = new List<SizeAxisTick>(tickDict.Values);
            tickList.Sort((a, b) => a.Value.CompareTo(b.Value));

            // Suppress label on Initial ticks, when its text are equal to first or last tick of type Title:
            int tickCount = tickList.Count;
            if (tickCount > 0 && tickList[0].TickType == AxisTickType.Initial)
            {
                SizeAxisTick initTick = tickList[0];
                SizeAxisTick titleTick = tickList.FirstOrDefault(t => t.TickType == AxisTickType.Title);
                if (titleTick != null && titleTick.Text == initTick.Text)
                    initTick.Text = "";
            }
            if (tickCount > 0 && tickList[tickCount - 1].TickType == AxisTickType.Initial)
            {
                SizeAxisTick initTick = tickList[tickCount - 1];
                SizeAxisTick titleTick = tickList.LastOrDefault(t => t.TickType == AxisTickType.Title);
                if (titleTick != null && titleTick.Text == initTick.Text)
                    initTick.Text = "";
            }

            this._TickList = tickList;
        }
        private Arrangement _Arrangement;
        private ArrangementSet _CurrentSet;
        private List<SizeAxisTick> _TickList;
        #endregion
        #region class Arrangement, ArrangementSet, ArrangementItem
        /// <summary>
        /// Set of all disponible SizeAxis arrangements
        /// </summary>
        protected class Arrangement
        {
            public Arrangement(GSizeAxis axis)
            {
                this.Axis = axis;

                List<ArrangementSet> setList = new List<ArrangementSet>();
                //                              pixel      minitick    větší    číslovaný  mezníkový  dělitel jednotka
                setList.Add(new ArrangementSet(0.000000m, 0.000001m, 0.000005m, 0.000010m, 0.000100m, 10.00m, "nm", this));
                setList.Add(new ArrangementSet(0.000001m, 0.000005m, 0.000010m, 0.000020m, 0.000100m, 20.00m, "nm", this));
                setList.Add(new ArrangementSet(0.000001m, 0.000010m, 0.000050m, 0.000100m, 0.001000m, 100.0m, "nm", this));
                setList.Add(new ArrangementSet(0.000001m, 0.000050m, 0.000100m, 0.000200m, 0.001000m, 200.0m, "nm", this));
                setList.Add(new ArrangementSet(0.000010m, 0.000100m, 0.000500m, 0.001000m, 0.010000m, 1.000m, "um", this));
                setList.Add(new ArrangementSet(0.000010m, 0.000500m, 0.001000m, 0.002000m, 0.010000m, 2.000m, "um", this));
                setList.Add(new ArrangementSet(0.000100m, 0.001000m, 0.005000m, 0.010000m, 0.100000m, 10.00m, "um", this));
                setList.Add(new ArrangementSet(0.000100m, 0.005000m, 0.010000m, 0.020000m, 0.100000m, 20.00m, "um", this));
                setList.Add(new ArrangementSet(0.001000m, 0.010000m, 0.050000m, 0.100000m, 1.000000m, 100.0m, "um", this));
                setList.Add(new ArrangementSet(0.001000m, 0.050000m, 0.100000m, 0.200000m, 1.000000m, 200.0m, "um", this));
                setList.Add(new ArrangementSet(0.010000m, 0.100000m, 0.500000m, 1.000000m, 10.00000m, 1.000m, "mm", this));
                setList.Add(new ArrangementSet(0.010000m, 0.500000m, 1.000000m, 2.000000m, 10.00000m, 2.000m, "mm", this));
                setList.Add(new ArrangementSet(0.100000m, 1.000000m, 5.000000m, 10.00000m, 100.0000m, 1.000m, "cm", this));
                setList.Add(new ArrangementSet(0.100000m, 5.000000m, 10.00000m, 20.00000m, 100.0000m, 2.000m, "cm", this));
                setList.Add(new ArrangementSet(1.000000m, 10.00000m, 50.00000m, 100.0000m, 1000.000m, 10.00m, "cm", this));
                setList.Add(new ArrangementSet(1.000000m, 50.00000m, 100.0000m, 200.0000m, 1000.000m, 20.00m, "cm", this));
                setList.Add(new ArrangementSet(10.00000m, 100.0000m, 500.0000m, 1000.000m, 10000.00m, 1.0000m, "m", this));
                setList.Add(new ArrangementSet(10.00000m, 500.0000m, 1000.000m, 2000.000m, 10000.00m, 2.0000m, "m", this));
                setList.Add(new ArrangementSet(100.0000m, 1000.000m, 5000.000m, 10000.00m, 100000.0m, 10.000m, "m", this));
                setList.Add(new ArrangementSet(100.0000m, 5000.000m, 10000.00m, 20000.00m, 100000.0m, 20.000m, "m", this));
                setList.Add(new ArrangementSet(1000.000m, 10000.00m, 50000.00m, 100000.0m, 1000000m, 100.000m, "m", this));
                setList.Add(new ArrangementSet(1000.000m, 50000.00m, 100000.0m, 200000.0m, 1000000m, 200.000m, "m", this));
                setList.Add(new ArrangementSet(10000.00m, 100000.0m, 500000.0m, 1000000.0m, 10000000m, 1.000m, "km", this));
                setList.Add(new ArrangementSet(10000.00m, 500000.0m, 1000000m, 2000000.0m, 10000000m, 2.000m, "km", this));

                this.SetList = setList;
            }
            public GSizeAxis Axis { get; private set; }
            public List<ArrangementSet> SetList { get; private set; }
            /// <summary>
            /// Select best ArrangementSet for specified scale (scale = number of milimeter per one pixel)
            /// </summary>
            /// <param name="scale"></param>
            /// <returns></returns>
            internal ArrangementSet SelectSetForScale(decimal scale)
            {
                ArrangementSet set = this.SetList.FirstOrDefault(s => s.GetDistanceSubTitleForScale(scale) >= 65m);
                if (set == null)
                    set = this.SetList[this.SetList.Count - 1];
                return set;
            }
        }
        /// <summary>
        /// One SizeAxis arrangement (set of four ticks = ArrangementItem)
        /// </summary>
        protected class ArrangementSet
        {
            /// <summary>
            /// Jedna konstelace osy
            /// </summary>
            /// <param name="pixelDist">Vzdálenost jednoho pixelu, pro zaokrouhlování</param>
            /// <param name="regularDist">Vzdálenost pravidelných ticků (jako milimetry), nemají popisek</param>
            /// <param name="significantDist">Vzdálenost větších ticků bez popisku (jako 5 milimetr)</param>
            /// <param name="subTitleDist">Vzdálenost ticků s popiskem (jako 1cm)</param>
            /// <param name="titleDist">Vzdálenost ticků s velkým popiskem (jako 10cm)</param>
            /// <param name="unit">Jednotka</param>
            /// <param name="owner"></param>
            public ArrangementSet(Decimal pixelDist, Decimal regularDist, Decimal significantDist, Decimal subTitleDist, Decimal titleDist,
                Decimal unitMultiplier, string unit, Arrangement owner)
            {
                string format = "##0";
                this.Owner = owner;
                this.InitialItem = new ArrangementItem(AxisTickType.Initial, 0m, subTitleDist, unitMultiplier, format, unit, this);
                this.TitleItem = new ArrangementItem(AxisTickType.Title, titleDist, subTitleDist, unitMultiplier, format, unit, this);
                this.SubTitleItem = new ArrangementItem(AxisTickType.SubTitle, subTitleDist, subTitleDist, unitMultiplier, format, "", this);
                this.SignificantTickItem = new ArrangementItem(AxisTickType.SignificantTick, significantDist, 0m, 0m, null, null, this);
                this.RegularTickItem = new ArrangementItem(AxisTickType.RegularTick, regularDist, 0m, 0m, null, null, this);
                this.PixelTickItem = new ArrangementItem(AxisTickType.PixelTick, pixelDist, 0m, 0m, null, null, this);
            }
            public override string ToString()
            {
                return "SignificantTick: " + this.SignificantTickItem.ToString();
            }
            /// <summary>
            /// Size axis
            /// </summary>
            public GSizeAxis Axis { get { return this.Owner.Axis; } }
            /// <summary>
            /// Owner of this one set = Arrangement, set of all arrangements
            /// </summary>
            public Arrangement Owner { get; private set; }
            /// <summary>
            /// Initial title on begin (and end) of axis
            /// </summary>
            public ArrangementItem InitialItem { get; private set; }
            /// <summary>
            /// Big tick, as 10 cm on ruler
            /// </summary>
            public ArrangementItem TitleItem { get; private set; }
            /// <summary>
            /// Standard numbered tick, as 1 cm on ruler
            /// </summary>
            public ArrangementItem SubTitleItem { get; private set; }
            /// <summary>
            /// Significant non-numbered tick, as 5 mm on ruler
            /// </summary>
            public ArrangementItem SignificantTickItem { get; private set; }
            /// <summary>
            /// Standard non-numbered tick, as 1 mm on ruler
            /// </summary>
            public ArrangementItem RegularTickItem { get; private set; }
            /// <summary>
            /// Nonvisible ticks, exists only for round time to "pixel" coordinates.
            /// </summary>
            public ArrangementItem PixelTickItem { get; private set; }
            /// <summary>
            /// Return distance between two tick of SubTitle type for specified scale (scale = number of milimeter per one pixel).
            /// </summary>
            /// <param name="scale"></param>
            /// <returns></returns>
            internal decimal GetDistanceSubTitleForScale(decimal scale)
            {
                return this.SubTitleItem.GetDistanceForScale(scale);
            }
            /// <summary>
            /// Returns specified decimal, rounded to specified Tick
            /// </summary>
            /// <param name="date"></param>
            /// <param name="roundToTick"></param>
            /// <returns></returns>
            internal Decimal? RoundValueToTick(Decimal date, AxisTickType roundToTick)
            {
                ArrangementItem item = GetArrangementItemForTick(roundToTick);
                return (item != null ? item.RoundValueToTick(date) : date);
            }
            /// <summary>
            /// Returns specified decimal, rounded to specified Tick
            /// </summary>
            /// <param name="date"></param>
            /// <param name="roundToTick"></param>
            /// <returns></returns>
            internal Decimal? RoundValueToTick(Decimal date, AxisTickType roundToTick, RoundMode roundMode)
            {
                ArrangementItem item = GetArrangementItemForTick(roundToTick);
                return (item != null ? item.RoundValueToTick(date, roundMode) : date);
            }
            /// <summary>
            /// Add tick for begin and end of timerange
            /// </summary>
            /// <param name="tickDict"></param>
            internal void AddInitialTicks(Dictionary<Decimal, SizeAxisTick> tickDict)
            {
                this.InitialItem.AddInitialTicks(tickDict);
            }
            /// <summary>
            /// Prepare Ticks for specified TickType to tick Dictionary
            /// </summary>
            /// <param name="tickType"></param>
            /// <param name="tickDict"></param>
            internal void CalculateTicksLine(AxisTickType tickType, Dictionary<Decimal, SizeAxisTick> tickDict)
            {
                ArrangementItem item = GetArrangementItemForTick(tickType);
                if (item != null)
                    item.CalculateTicksLine(tickDict);
            }
            /// <summary>
            /// Returns instance of ArrangementItem for specified Tick type
            /// </summary>
            /// <param name="tickType"></param>
            /// <returns></returns>
            internal ArrangementItem GetArrangementItemForTick(AxisTickType tickType)
            {
                switch (tickType)
                {
                    case AxisTickType.PixelTick: return this.PixelTickItem;
                    case AxisTickType.RegularTick: return this.RegularTickItem;
                    case AxisTickType.SignificantTick: return this.SignificantTickItem;
                    case AxisTickType.SubTitle: return this.SubTitleItem;
                    case AxisTickType.Title: return this.TitleItem;
                }
                return null;
            }
        }
        /// <summary>
        /// Specification of one tick on one SizeAxis arrangement: interval, time format string, support for calculations
        /// </summary>
        protected class ArrangementItem
        {
            public ArrangementItem(AxisTickType tickType, Decimal interval,
                Decimal unitDivider, Decimal unitMultiplier, string format, string unit, ArrangementSet owner)
            {
                this.Owner = owner;
                this.TickType = tickType;
                this.Interval = interval;
                this.UnitDivider = unitDivider;
                this.UnitMultiplier = unitMultiplier;
                this.Format = format;
                this.Unit = unit;
            }
            public override string ToString()
            {
                return this.TickType.ToString() + "; " + this.Interval.ToString();
            }
            /// <summary>
            /// Size axis
            /// </summary>
            public GSizeAxis Axis { get { return this.Owner.Axis; } }
            /// <summary>
            /// Owner of this one set = ArrangementSet, set of arrangements of four TickType
            /// </summary>
            public ArrangementSet Owner { get; private set; }
            /// <summary>
            /// Type of this ticks
            /// </summary>
            public AxisTickType TickType { get; private set; }
            /// <summary>
            /// Interval between ticks
            /// </summary>
            public Decimal Interval { get; private set; }
            /// <summary>
            /// Divider from Value to Format (Interval is in milimeter, 
            /// Format is for example in meter: FormatDivider = 1000m (Value / FormatDivider = FormatValue))
            /// </summary>
            public Decimal UnitDivider { get; private set; }
            /// <summary>
            /// Divider from Value to Format (Interval is in milimeter, 
            /// Format is for example in meter: FormatDivider = 1000m (Value / FormatDivider = FormatValue))
            /// </summary>
            public Decimal UnitMultiplier { get; private set; }
            /// <summary>
            /// User string format for display time on this ticks
            /// </summary>
            public string Format { get; private set; }
            /// <summary>
            /// Measure unit (um, mm, cm, m, km)
            /// </summary>
            public string Unit { get; private set; }
            /// <summary>
            /// Return distance between two tick of this type for specified scale (scale = number of milimeter per one pixel).
            /// </summary>
            /// <param name="scale"></param>
            /// <returns></returns>
            internal decimal GetDistanceForScale(decimal scale)
            {
                return this.Interval / scale;
            }
            /// <summary>
            /// Standard length of tick between 0 and 1. 
            /// Title has length = 1.00, SubTitle = 0.90, SignificantTick = 0.75, RegularTick = 0.60.
            /// </summary>
            public decimal TickSize
            {
                get
                {
                    if (!this._TickLength.HasValue)
                    {
                        switch (this.TickType)
                        {
                            case AxisTickType.Initial:
                                this._TickLength = 2.00m;
                                break;
                            case AxisTickType.Title:
                                this._TickLength = 1.00m;
                                break;
                            case AxisTickType.SubTitle:
                                this._TickLength = 0.90m;
                                break;
                            case AxisTickType.SignificantTick:
                                this._TickLength = 0.75m;
                                break;
                            case AxisTickType.RegularTick:
                                this._TickLength = 0.60m;
                                break;
                            default:
                                this._TickLength = 0.50m;
                                break;
                        }
                    }
                    return this._TickLength.Value;
                }
            }
            private decimal? _TickLength;
            /// <summary>
            /// Returns specified decimal to this ticks.
            /// </summary>
            /// <param name="value"></param>
            /// <returns></returns>
            internal Decimal? RoundValueToTick(Decimal value)
            {
                return SizeRange.RoundValue(value, this.Interval, RoundMode.Math);
            }
            /// <summary>
            /// Returns specified decimal to this ticks.
            /// </summary>
            /// <param name="value"></param>
            /// <returns></returns>
            internal Decimal? RoundValueToTick(Decimal value, RoundMode roundMode)
            {
                return SizeRange.RoundValue(value, this.Interval, roundMode);
            }
            /// <summary>
            /// Add tick for begin and end of timerange
            /// </summary>
            /// <param name="tickDict"></param>
            internal void AddInitialTicks(Dictionary<Decimal, SizeAxisTick> tickDict)
            {
                SizeRange sizeRange = this.Axis.Value;
                Decimal? begin = this.Owner.RoundValueToTick(sizeRange.Begin.Value, AxisTickType.SubTitle, RoundMode.Floor);
                string textBegin = this.GetFormatText(begin.Value);
                this.AddOneTick(tickDict, sizeRange.Begin.Value, textBegin, AxisTickAlignment.Begin);

                Decimal? end = this.Owner.RoundValueToTick(sizeRange.End.Value, AxisTickType.SubTitle, RoundMode.Ceiling);
                string textEnd = this.GetFormatText(end.Value);
                if (textEnd != textBegin)
                    this.AddOneTick(tickDict, sizeRange.End.Value, textEnd, AxisTickAlignment.End);
            }
            /// <summary>
            /// Store to Dictionary (tickDict) all ticks for current item and current state of Axis (SizeRange, Scale).
            /// When Dictionary contains a key for any time on line of this item, does not add new tick.
            /// Dictionary must be filled from top to bottom (from Title to Regular items).
            /// </summary>
            /// <param name="tickDict"></param>
            internal void CalculateTicksLine(Dictionary<Decimal, SizeAxisTick> tickDict)
            {
                if (this.Interval == 0L) return;

                SizeRange sizeRange = this.Axis.Value;
                Decimal value = SizeRange.RoundValue(sizeRange.Begin.Value, this.Interval, RoundMode.Ceiling);
                while (value <= sizeRange.End.Value)
                {
                    string text = this.GetFormatText(value);
                    this.AddOneTick(tickDict, value, text, AxisTickAlignment.Center);
                    value = value + this.Interval;
                }
            }
            private string GetFormatText(Decimal value)
            {
                if (this.Format == null || this.UnitMultiplier == 0m) return "";
                decimal formatValue = value / this.UnitDivider * this.UnitMultiplier;
                string text = formatValue.ToString(this.Format) + this.Unit;
                return text;
            }
            private void AddOneTick(Dictionary<Decimal, SizeAxisTick> tickDict, Decimal value, string text, AxisTickAlignment alignment)
            {
                if (!tickDict.ContainsKey(value))
                {
                    SizeAxisTick tick = new SizeAxisTick(this.TickType, value, this.Axis.GetRelativePixelForValue(value).Value, this.TickSize, text, alignment);
                    tickDict.Add(value, tick);
                }
            }
        }
        #endregion
    }
    /// <summary>
    /// One real tick on SizeAxis
    /// </summary>
    public class SizeAxisTick : AxisTick<Decimal>
    {
        public SizeAxisTick(AxisTickType tickType, Decimal value, int pixel, decimal tickSize, string text, AxisTickAlignment alignment)
            : base(tickType, value, pixel, tickSize, text, alignment)
        { }
    }

    #region Common Axis members (for SizeAxis and TimeAxis)
    /// <summary>
    /// One real tick on any Axis (generic value)
    /// </summary>
    public class AxisTick<T>
    {
        public AxisTick(AxisTickType tickType, T value, int pixel, decimal tickSize, string text, AxisTickAlignment alignment)
        {
            this.TickType = tickType;
            this.Value = value;
            this.Pixel = pixel;
            this.TickSize = tickSize;
            this.Text = text;
            this.Alignment = alignment;
        }
        public override string ToString()
        {
            return this.Text + "; X=" + this.Pixel.ToString() + "; " + this.TickType.ToString();
        }
        /// <summary>
        /// Type of this tick
        /// </summary>
        public AxisTickType TickType { get; private set; }
        /// <summary>
        /// Logical value of tick
        /// </summary>
        public T Value { get; private set; }
        /// <summary>
        /// Cordinate of tick in pixels, relative to the begin of axis
        /// </summary>
        public int Pixel { get; private set; }
        /// <summary>
        /// Standard length of tick between 0 and 1. 
        /// Title has length = 1.00, SubTitle = 0.90, SignificantTick = 0.75, RegularTick = 0.60.
        /// </summary>
        public decimal TickSize { get; private set; }
        /// <summary>
        /// Displayed value of tick
        /// </summary>
        public string Text { get; set; }
        /// <summary>
        /// Alignement of label
        /// </summary>
        public AxisTickAlignment Alignment { get; private set; }
    }
    /// <summary>
    /// Type of tick
    /// </summary>
    public enum AxisTickType
    {
        None = 0,
        PixelTick,
        RegularTick,
        SignificantTick,
        SubTitle,
        Title,
        Initial
    }
    /// <summary>
    /// Type of alignment of tick
    /// </summary>
    public enum AxisTickAlignment
    {
        None = 0,
        Begin,
        Center,
        End
    }
    /// <summary>
    /// Orientace osy vzhledem k datové oblasti
    /// </summary>
    public enum AxisOrientation
    {
        /// <summary>
        /// Osa je nad daty: vodorovná, menší hodnoty vlevo, větší doprava, ticky jsou na dolním okraji
        /// </summary>
        Top = 0,
        /// <summary>
        /// Osa je vlevo od dat: svislá, orientace hodnot zdola nahoru (nula je dole, nahoru jdou kladná čísla), ticky jsou na pravém okraji
        /// </summary>
        LeftUp,
        /// <summary>
        /// Osa je vlevo od dat: svislá, orientace hodnot zhora dolů (nula je nahoře, dolů jdou kladná čísla), ticky jsou na pravém okraji
        /// </summary>
        LeftDown,
        /// <summary>
        /// Osa je vpravo od dat: svislá, orientace hodnot zdola nahoru (nula je dole, nahoru jdou kladná čísla), ticky jsou na levém okraji
        /// </summary>
        RightUp,
        /// <summary>
        /// Osa je vpravo od dat: svislá, orientace hodnot zhora dolů (nula je nahoře, dolů jdou kladná čísla), ticky jsou na levém okraji
        /// </summary>
        RightDown,
        /// <summary>
        /// Osa je pod daty: vodorovná, menší hodnoty vlevo, větší doprava, ticky jsou na horním okraji
        /// </summary>
        Bottom
    }
    /// <summary>
    /// 
    /// </summary>
    public enum AxisSynchronizeMode
    {
        /// <summary>
        /// No synchronize
        /// </summary>
        None = 0,
        /// <summary>
        /// Synchronize Scale, no Range
        /// </summary>
        Scale,
        /// <summary>
        /// Synchronize Range
        /// </summary>
        Range
    }
    #endregion
}
